#![allow(non_snake_case)]
use std::collections::HashMap;

use crate::{components::download_xlsx_file_with_url, shhr::SelectIds};

use super::server_error::ServerError;
use either_of::Either;
use leptos::prelude::*;
use serde_json::Value;

use shq_common::prelude::{
    report::DisplayCols, Column, ColumnCells, ExportQueryParams, ListProps, OrderItems, TableFooterShowProps, TaskType, User, WhereCause, WhereCauses
};
use shq_web_controls::{
    use_message, ColumnShows, ListTableServer, MessageOptions, MessageVariant, OrderFilterModel,
    OrderFliterList, TableFooter, TableState, TreeItems,
};
use thaw_utils::ArcOneCallback;
#[slot]
pub struct TableToolbar {
    children: Children,
}
#[component]
pub fn ServerDataTable(
    menu_id: u32,
    #[prop(into)] conf_name: String,
    #[prop(into)] title: Option<String>,
    #[prop(into)] list_props: StoredValue<ListProps>,
    #[prop(into)] field_props: StoredValue<Vec<Column>>,
    #[prop(optional, into)] init_filters: Signal<Vec<WhereCause>>,
    #[prop(optional, into)] on_select_change: Option<ArcOneCallback<Vec<(usize, Value)>>>,
    #[prop(optional, into)] can_export: bool,
    #[prop(default=None)] table_toolbar: Option<TableToolbar>,
    #[prop(optional, into)] is_refresh: RwSignal<bool>,
    #[prop(optional, into)] flow_task_type: MaybeProp<TaskType>,
    #[prop(optional, into)] footer_show_props: TableFooterShowProps,
) -> impl IntoView {
    let curr_user = use_context::<RwSignal<Option<User>>>().unwrap();
    let conf_name = StoredValue::new(conf_name);
    let inti_table_state =
        move || field_props.with_value(|cols| TableState::from_columns(conf_name, cols, None));
    let (table_state, _) = signal(inti_table_state());
    Effect::new(move || {
        table_state.with_untracked(|ts| {
            ts.set_init_filters(init_filters.get());
        });
    });
    Effect::new(move || {
        table_state.with_untracked(|ts| {
            if let Some(on_select_change) = on_select_change.as_ref() {
                ts.list_data().with(|list| {
                    let selected_rows = ts.selected_indexs().get();
                    if selected_rows.is_empty() {
                        on_select_change(vec![]);
                        return;
                    }
                    let selected_values: Vec<(usize, Value)> = selected_rows
                        .iter()
                        .map(|idx| {
                            let v = list.get(*idx).cloned().unwrap_or(Value::Null);
                            (*idx, v)
                        })
                        .collect();
                    on_select_change(selected_values);
                })
            }
        });
    });
    let select_ids = use_context::<WriteSignal<SelectIds>>();
    let pk_col = StoredValue::new(
        field_props
            .with_value(|cols| {
                cols.iter()
                    .find(|c| c.is_pk)
                    .map(|c| c.name.clone().unwrap_or_default())
            })
            .expect("没有找到主键列"),
    );
    Effect::new(move || {
        if let Some(select_ids) = select_ids {
            table_state.with_untracked(|ts| {
                let idxs = ts.selected_indexs();
                let pk_col = pk_col.get_value();
                let ids = idxs.with(|idxs| {
                    ts.list_data().with_untracked(|ld| {
                        let ids: Vec<u64> = idxs
                            .iter()
                            .map(|i| ld[*i].get(&pk_col).and_then(Value::as_u64).unwrap_or(0))
                            .collect();
                        ids
                    })
                });
                select_ids.set(SelectIds::from(ids));
            });
        }
    });
    provide_context(table_state);
    let page_size = table_state.with_untracked(|ts| ts.page_size());
    let page = table_state.with_untracked(|ts| ts.page());
    let display_sigal = table_state.with_untracked(|ts| ts.display());
    let header_cells = Memo::new(move |_| {
        display_sigal.with(|ds| {
            let display_cols: Vec<&str> = ds.iter().map(|s| s.as_str()).collect();
            field_props.with_value(|cols| ColumnCells::from_columns(cols, &display_cols))
        })
    });
    let orders = Memo::new(move |_| {
        table_state.with_untracked(|ts| {
            let orders = ts.get_orders();
            if orders.is_empty() {
                list_props.with_value(|lp| lp.init_sorter.clone())
            } else {
                Some(OrderItems::from(orders))
            }
        })
    });
    let filters = Memo::new(move |_| {
        table_state.with_untracked(|ts| {
            let filters = ts.get_filters();
            if filters.is_empty() {
                list_props.with_value(|lp| lp.seeks.clone())
            } else {
                Some(WhereCauses::from(filters))
            }
        })
    });
    let mess = use_message();
    let json_vals = LocalResource::new(move || {
        get_list_data(
            menu_id,
            conf_name.get_value(),
            filters.with(|f| f.as_ref().map(|s| serde_json::to_string(s).unwrap())),
            orders.with(|o| o.as_ref().map(|s| serde_json::to_string(s).unwrap())),
            display_sigal.get(),
            page.get() as u64,
            page_size.get() as u64,
            flow_task_type.get(),
        )
    });
    Effect::new(move || {
        let refresh = is_refresh.get();
        if refresh {
            table_state.with_untracked(|ts| {
                ts.set_selected_indexs(vec![]);
            });
            is_refresh.set(false);
            table_state.with_untracked(|ts| {
                ts.set_page(ts.page().get());
            });
        }
    });
    let export = Action::new(move |&()| {
        let name = title.to_owned().unwrap_or(conf_name.get_value());
        let cols = header_cells.with(|hc| {
            hc.get_leaf_cells()
                .iter()
                .map(|c| c.name.clone())
                .collect::<Vec<String>>()
        });
        let filters = filters.with(|f| f.as_ref().map(|s| serde_json::to_string(s).unwrap()));
        let orders = orders.with(|o| o.as_ref().map(|s| serde_json::to_string(s).unwrap()));
        let user_id = curr_user.with(|u| u.as_ref().map(|u| u.id)).unwrap();
        async move {
            let eqp = ExportQueryParams {
                conf_name: conf_name.get_value(),
                display_cols: DisplayCols::Part(cols),
                filters,
                orders,
                user_id,
                menu_id,
            };
            let params = serde_qs::to_string(&eqp).unwrap();
            if let Err(err) =
                download_xlsx_file_with_url(&name, "xlsx","/export_xlsx_with_dao", Some(params))
            {
                mess.create(err, MessageVariant::Error, MessageOptions::default());
            }
        }
    });
    let opts_map = Memo::new(move |_| {
        let opts_map: HashMap<String, StoredValue<TreeItems>> = field_props.with_value(|f| {
            f.iter()
                .filter(|c| {
                    c.edit_props
                        .as_ref()
                        .map(|ep| ep.options.is_some())
                        .unwrap_or(false)
                })
                .map(|c| {
                    let opts = c.edit_props.as_ref().unwrap().options.as_ref().unwrap();
                    (
                        c.name.clone().unwrap(),
                        StoredValue::new(TreeItems::from_options(opts)),
                    )
                })
                .collect()
        });
        opts_map
    });
    let tree_items = field_props.with_value(|cols| StoredValue::new(TreeItems::from(cols)));
    view! {
        {if let Some(tt) = table_toolbar {
            view! { {(tt.children)()} }.into()
        } else {
            None
        }}
        <Transition fallback=|| view! { <progress class="progress w-56"></progress> }>
            <Show
                when= move || json_vals.get().as_deref().is_some()
                fallback=|| view! { <p>"Loading..."</p> }
            >
                {
                    match json_vals.get().as_deref().cloned().unwrap() {
                        Ok(resp) => {
                            let multi_sel=list_props.with_value(|lp| lp.multi_select);
                            table_state.with_untracked(|ts| {
                                ts.set_list_data(resp.0);
                            });
                            let count = resp.1;
                            let table_footer = if can_export {
                                view! {<TableFooter total=count as usize show_props=footer_show_props export_click=ArcOneCallback::new(move|_|{export.dispatch(());})/>}
                            } else {
                                view! {<TableFooter total=count as usize show_props=footer_show_props/>}
                            };
                            Either::Left(view!{
                                <div class="flex-1 bg-base-100 overflow-auto relative h-full w-full">
                                    <ListTableServer header_cells multi_sel cell_border=true/>
                                </div>
                                {table_footer}
                            })
                        }
                        Err(err) => Either::Right(view! {
                            <div class="flex-1 bg-base-100 overflow-auto mx-2">
                                <ServerError err_msg=err.to_string().into()/>
                            </div>
                        }),
                    }
                }
            </Show>
        </Transition>
        <OrderFilterModel header_cells opts_map=opts_map/>
        <ColumnShows tree_items/>
        <OrderFliterList tree_items/>
    }
}

#[server(GetListData, "/api/protect")]
pub async fn get_list_data(
    menu_id: u32,
    conf_name: String,
    filters: Option<String>,
    orders: Option<String>,
    display_cols: Vec<String>,
    page: u64,
    page_size: u64,
    flow_task_type: Option<TaskType>,
) -> Result<(Vec<Value>, u64), ServerFnError> {
    use crate::auth::get_user_context_with_auth;
    let user = get_user_context_with_auth(menu_id, shq_common::prelude::AuthType::Select).await?;
    let fs: Option<WhereCauses> = filters.map(|f| serde_json::from_str(&f).unwrap());
    let os: Option<OrderItems> = orders.map(|o| serde_json::from_str(&o).unwrap());
    let ps = shq_pas_backend::PageService::new(&conf_name, user.user_id, menu_id);
    let display_cols = DisplayCols::Part(display_cols);
    let res = ps
        .get_list_data(fs, os, display_cols, page, page_size, flow_task_type)
        .await;
    match res {
        Ok(mut data) => {
            if let Some(task_type) = flow_task_type {
                append_flow_columns(&mut data.0, task_type).await?;
            }
            Ok(data)
        }
        Err(e) => Err(ServerFnError::ServerError(e.to_string())),
    }
}
#[cfg(feature = "ssr")]
async fn append_flow_columns(
    list_data: &mut Vec<Value>,
    task_type: TaskType,
) -> Result<(), ServerFnError> {
    for row in list_data.iter_mut() {
        if let Some(run_id) = row.get("frid").and_then(Value::as_u64) {
            use shq_common::prelude::FlowState;

            let info = shq_workflow::get_flow_run_props(run_id)
                .await
                .map_err(|e| ServerFnError::new(e.to_string()))?;
            let wf_conf = shq_workflow::get_workflow_config(info.wfid, info.version).ok_or(
                ServerFnError::new(format!(
                    "wfid:{},version:{}没有找到",
                    info.wfid, info.version
                )),
            )?;
            let step = wf_conf
                .get_step(info.step_id)
                .ok_or(ServerFnError::new(format!(
                    "步骤id:{}没有找到",
                    info.step_id
                )))?;
            let can_back = info.state == FlowState::Runing
                && task_type == TaskType::Doing
                && step.back_to_steps.len() > 0;
            let can_take_back = info.state == FlowState::Completed
                && task_type == TaskType::Done
                && step.can_take_back;
            let can_suspend = info.state == FlowState::Suspended
                && (task_type == TaskType::Doing || task_type == TaskType::Manager)
                && step.can_suspend;
            let can_delete =
                info.state == FlowState::Runing && task_type == TaskType::Doing && step.id == 1;
            let data_map = row.as_object_mut().unwrap();
            data_map.insert("can_back".to_string(), Value::from(can_back));
            data_map.insert("can_take_back".to_string(), Value::from(can_take_back));
            data_map.insert("can_suspend".to_string(), Value::from(can_suspend));
            data_map.insert("can_delete".to_string(), Value::from(can_delete));
        }
    }
    Ok(())
}
pub(super) fn get_column_by_name<'a>(
    cols: &'a Vec<Column>,
    col_name: &'a str,
) -> Option<&'a Column> {
    for col in cols.iter() {
        if col.name.as_ref().map(|n| n == col_name).unwrap_or(false) {
            return Some(col);
        }
        let ccols = &col.children;
        if ccols.len() > 0 {
            let col = get_column_by_name(ccols, col_name);
            if let Some(col) = col {
                return Some(col);
            }
        }
    }
    None
}
